/*  see copyright notice in squirrel.h */
#ifndef _SQCLASS_H_
#define _SQCLASS_H_

struct SQInstance;

struct SQClassMember
{
	SQObjectPtr val;
	SQObjectPtr attrs;
	void Null()
	{
		val.Null();
		attrs.Null();
	}
};

typedef sqvector<SQClassMember> SQClassMemberVec;

#define MEMBER_TYPE_METHOD 0x01000000
#define MEMBER_TYPE_FIELD 0x02000000
#define MEMBER_MAX_COUNT 0x00FFFFFF

#define _ismethod(o) (_integer(o) & MEMBER_TYPE_METHOD)
#define _isfield(o) (_integer(o) & MEMBER_TYPE_FIELD)
#define _make_method_idx(i) ((SQInteger)(MEMBER_TYPE_METHOD | i))
#define _make_field_idx(i) ((SQInteger)(MEMBER_TYPE_FIELD | i))
#define _member_type(o) (_integer(o) & 0xFF000000)
#define _member_idx(o) (_integer(o) & 0x00FFFFFF)

struct SQClass : public CHAINABLE_OBJ
{
	SQClass(SQSharedState *ss, SQClass *base);

public:
	static SQClass *Create(SQSharedState *ss, SQClass *base)
	{
		SQClass *newclass = (SQClass *)SQ_MALLOC(sizeof(SQClass));
		new(newclass) SQClass(ss, base);
		return newclass;
	}
	~SQClass();
	bool NewSlot(SQSharedState *ss, const SQObjectPtr &key,
			const SQObjectPtr &val, bool bstatic);
	bool Get(const SQObjectPtr &key, SQObjectPtr &val)
	{
		if(_members->Get(key, val)) {
			if(_isfield(val)) {
				SQObjectPtr &o = _defaultvalues[_member_idx(val)].val;
				val = _realval(o);
			} else {
				val = _methods[_member_idx(val)].val;
			}
			return true;
		}
		return false;
	}
	bool GetConstructor(SQObjectPtr &ctor)
	{
		if(_constructoridx != -1) {
			ctor = _methods[_constructoridx].val;
			return true;
		}
		return false;
	}
	bool SetAttributes(const SQObjectPtr &key, const SQObjectPtr &val);
	bool GetAttributes(const SQObjectPtr &key, SQObjectPtr &outval);
	void Lock()
	{
		_locked = true;
		if(_base)
			_base->Lock();
	}
	void Release()
	{
		if(_hook) {
			_hook(_typetag, 0);
		}
		sq_delete(this, SQClass);
	}
	void Finalize();
#ifndef NO_GARBAGE_COLLECTOR
	void Mark(SQCollectable **);
	SQObjectType GetType()
	{
		return OT_CLASS;
	}
#endif
	SQInteger Next(const SQObjectPtr &refpos, SQObjectPtr &outkey,
			SQObjectPtr &outval);
	SQInstance *CreateInstance();
	SQTable *_members;
	SQClass *_base;
	SQClassMemberVec _defaultvalues;
	SQClassMemberVec _methods;
	SQObjectPtr _metamethods[MT_LAST];
	SQObjectPtr _attributes;
	SQUserPointer _typetag;
	SQRELEASEHOOK _hook;
	bool _locked;
	SQInteger _constructoridx;
	SQInteger _udsize;
};

#define calcinstancesize(_theclass_)                                           \
	(_theclass_->_udsize                                                       \
			+ sq_aligning(sizeof(SQInstance)                                   \
						  + (sizeof(SQObjectPtr)                               \
								  * (_theclass_->_defaultvalues.size() > 0     \
												  ? _theclass_->_defaultvalues \
																	.size()    \
															- 1                \
												  : 0))))

struct SQInstance : public SQDelegable
{
	void Init(SQSharedState *ss);
	SQInstance(SQSharedState *ss, SQClass *c, SQInteger memsize);
	SQInstance(SQSharedState *ss, SQInstance *c, SQInteger memsize);

public:
	static SQInstance *Create(SQSharedState *ss, SQClass *theclass)
	{

		SQInteger size = calcinstancesize(theclass);
		SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
		new(newinst) SQInstance(ss, theclass, size);
		if(theclass->_udsize) {
			newinst->_userpointer =
					((unsigned char *)newinst) + (size - theclass->_udsize);
		}
		return newinst;
	}
	SQInstance *Clone(SQSharedState *ss)
	{
		SQInteger size = calcinstancesize(_class);
		SQInstance *newinst = (SQInstance *)SQ_MALLOC(size);
		new(newinst) SQInstance(ss, this, size);
		if(_class->_udsize) {
			newinst->_userpointer =
					((unsigned char *)newinst) + (size - _class->_udsize);
		}
		return newinst;
	}
	~SQInstance();
	bool Get(const SQObjectPtr &key, SQObjectPtr &val)
	{
		if(_class->_members->Get(key, val)) {
			if(_isfield(val)) {
				SQObjectPtr &o = _values[_member_idx(val)];
				val = _realval(o);
			} else {
				val = _class->_methods[_member_idx(val)].val;
			}
			return true;
		}
		return false;
	}
	bool Set(const SQObjectPtr &key, const SQObjectPtr &val)
	{
		SQObjectPtr idx;
		if(_class->_members->Get(key, idx) && _isfield(idx)) {
			_values[_member_idx(idx)] = val;
			return true;
		}
		return false;
	}
	void Release()
	{
		_uiRef++;
		if(_hook) {
			_hook(_userpointer, 0);
		}
		_uiRef--;
		if(_uiRef > 0)
			return;
		SQInteger size = _memsize;
		this->~SQInstance();
		SQ_FREE(this, size);
	}
	void Finalize();
#ifndef NO_GARBAGE_COLLECTOR
	void Mark(SQCollectable **);
	SQObjectType GetType()
	{
		return OT_INSTANCE;
	}
#endif
	bool InstanceOf(SQClass *trg);
	bool GetMetaMethod(SQVM *v, SQMetaMethod mm, SQObjectPtr &res);

	SQClass *_class;
	SQUserPointer _userpointer;
	SQRELEASEHOOK _hook;
	SQInteger _memsize;
	SQObjectPtr _values[1];
};

#endif //_SQCLASS_H_
